﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FeatureModelCore.FMShapes;

namespace FeatureModelTool.FMAlgorithms.FMGraphicAlgorithms
{
    class AngleCompare : Comparer<FMAngle>
    {
        public override int Compare(FMAngle x, FMAngle y)
        {
            if (x.AngleRadian > y.AngleRadian)
            {
                return 1;
            }
            if (x.AngleRadian == y.AngleRadian)
            {
                return 0;
            }
            return -1;
        }
    }
    public class GraphicAngleAlgorithms
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="center"></param>
        /// <param name="lineEndPoints"></param>
        /// <param name="lineLength"></param>
        /// <param name="clockwise"></param>
        /// <param name="isLarge"></param>
        /// <param name="startPoint"></param>
        /// <param name="endPoint"></param>
        public static void CalculateCoverAngle(FMPoint center
            , FMPoint[] lineEndPoints, double lineLength, out bool clockwise, out bool isLarge,
            out FMPoint startPoint, out FMPoint endPoint)
        {
            clockwise = false;
            isLarge = false;
            List<FMAngle> allPoints = new List<FMAngle>();
            foreach (FMPoint p in lineEndPoints)
            {
                double angle = CalculateAngle(p, center);
                allPoints.Add(new FMAngle() { PointA = p, PointB = center, AngleRadian = angle });
            }
            allPoints.Sort(new AngleCompare());
            List<FMAngle> pointAngleList = new List<FMAngle>();
            FMAngle pa = new FMAngle();
            pa.PointA = allPoints[allPoints.Count - 1].PointA;
            pa.PointB = allPoints[0].PointA;
            pa.AngleRadian = allPoints[0].AngleRadian
                - allPoints[allPoints.Count - 1].AngleRadian + Math.PI * 2;
            pointAngleList.Add(pa);
            for (int i = 1; i != allPoints.Count; ++i)
            {
                FMAngle newpa = new FMAngle();
                newpa.PointA = allPoints[i - 1].PointA;
                newpa.PointB = allPoints[i].PointA;
                newpa.AngleRadian = allPoints[i].AngleRadian - allPoints[i - 1].AngleRadian;
                pointAngleList.Add(newpa);
            }

            pointAngleList.Sort(new AngleCompare());
            FMAngle ret = pointAngleList[pointAngleList.Count - 1];
            endPoint = CalculatePointOnline(center, ret.PointA, lineLength);
            startPoint = CalculatePointOnline(center, ret.PointB, lineLength);
            clockwise = true;
            if (ret.AngleRadian > Math.PI)
            {
                isLarge = false;
            }
            else
            {
                isLarge = true;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="radian"></param>
        public static void ValidRadian(ref double radian)
        {
            if (radian < 0)
            {
                radian += Math.PI * 2;
            }
            if (radian >= Math.PI * 2)
            {
                radian -= Math.PI * 2;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="startPoint"></param>
        /// <param name="endPoint"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public static FMPoint CalculatePointOnline(FMPoint startPoint, FMPoint endPoint, double length)
        {
            double d = CalculationDestination(startPoint, endPoint);
            double x = startPoint.X + (endPoint.X - startPoint.X) / d * length;
            double y = startPoint.Y + (endPoint.Y - startPoint.Y) / d * length;
            FMPoint ret = new FMPoint(x, y);

            return ret;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="centerPoint"></param>
        /// <param name="r"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public static FMPoint CalculatePoint(FMPoint centerPoint, double r, double length)
        {
            if (r < 0 || r >= Math.PI * 2)
            {
                return null;
            }
            double X, Y;
            if (r <= Math.PI / 2)
            {
                X = centerPoint.X + Math.Cos(r) * length;
                Y = centerPoint.Y + Math.Sin(r) * length;
                return new FMPoint(X, Y);
            }
            else if (r <= Math.PI)
            {
                r -= Math.PI / 2;
                X = centerPoint.X - Math.Sin(r) * length;
                Y = centerPoint.Y + Math.Cos(r) * length;
                return new FMPoint(X, Y);
            }
            else if (r <= Math.PI / 2 * 3)
            {
                r -= Math.PI;
                X = centerPoint.X - Math.Cos(r) * length;
                Y = centerPoint.Y - Math.Sin(r) * length;
                return new FMPoint(X, Y);
            }
            else
            {
                r -= Math.PI / 2 * 3;
                X = centerPoint.X + Math.Sin(r) * length;

                Y = centerPoint.Y - Math.Cos(r) * length;
                return new FMPoint(X, Y);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="point"></param>
        /// <param name="center"></param>
        /// <returns></returns>
        public static double CalculateAngle(FMPoint point, FMPoint center)
        {
            double dis = CalculationDestination(point, center);
            double ret = 0;
            int block = CalculatePointBlock(point, center);
            switch (block)
            {
                case 1:
                    {
                        ret = Math.Abs(point.Y - center.Y) / dis;
                        break;
                    }
                case 2:
                    {
                        ret = Math.Abs(center.X - point.X) / dis;
                        break;
                    }
                case 3:
                    {
                        ret = Math.Abs(center.Y - point.Y) / dis;
                        break;
                    }
                case 4:
                    {
                        ret = Math.Abs(point.X - center.X) / dis;
                        break;
                    }
            }
            return Math.Asin(ret) + (block - 1) * Math.PI / 2;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public static int CalculatePointBlock(FMPoint point, FMPoint center)
        {
            double detx, dety;
            detx = center.X - point.X;
            dety = center.Y - point.Y;
            if (dety <= 0)
            {
                if (detx <= 0)
                {
                    return 1;
                }
                else
                {
                    return 2;
                }
            }
            else
            {
                if (detx >= 0)
                {
                    return 3;
                }
                else
                {
                    return 4;
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static double CalculationDestination(FMPoint start, FMPoint end)
        {
            return Math.Sqrt((start.X - end.X) * (start.X - end.X)
                + (start.Y - end.Y) * (start.Y - end.Y));
        }
    }
}
